// *********************************************************
//      Copyright (c) Kosta Gaitanis
//      All rights reserved
//
//      k.gaitanis@gmail.com
//
// *********************************************************

#include <QtOpenGL/QtOpenGL>
#include <QDebug>
#include <QLayout>
#include <QTimeLine>

#include "Camera.h"
#include "MainWindow.h"

// Target Frames per Second
static const double FPS = 30.0;

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),
      m_glWidget(new GLWidget()),
      m_world(new World()),
      m_itemsWidget(new ItemsListWidget()),
      m_materialWidget(new MaterialWidget()),
      m_lightsWidget(new LightsListWidget()),
      m_textureWidget(new TextureListWidget()),
      m_renderWidget(new RenderWidget()),
      m_timeLine(new QTimeLine(60000, this)),
      m_playPauseButton(new QPushButton("Pause")),
      m_speedSpinBox(new QDoubleSpinBox()),
      m_cameraWidget(new CameraWidget(m_world->getCamera())),
      m_gammaCorrection(new QDoubleSpinBox())
{
    setMinimumSize(800, 600);

    createMainContent();
    makeSignalConnections();

    m_timeLine->setFrameRange(0, FPS * m_timeLine->duration() / 1000.0);
    m_timeLine->setLoopCount(0); // loop forever
    m_timeLine->setCurveShape(QTimeLine::LinearCurve);
    m_timeLine->start();
}

MainWindow::~MainWindow()
{

}

Camera *MainWindow::getCamera() const
{
    return m_world->getCamera();
}

World *MainWindow::getWorld() const
{
    return m_world;
}

void MainWindow::addItem(Item *item)
{
    m_world->addItem(item);
    m_itemsWidget->setRootItem(m_world->getWorldItem());
}

void MainWindow::addLight(Light *light)
{
    m_world->addLight(light);
    m_lightsWidget->addLight(light);

}

void MainWindow::addTexture(const Texture *texture)
{
    if (!m_world->hasTexture(texture)) {
        m_textureWidget->addTexture(texture);
    }
    m_world->addTexture(texture);
}

void MainWindow::bindTexture(const Texture *texture, Item *item)
{
    if (!m_world->hasTexture(texture)) {
        m_textureWidget->addTexture(texture);
    }
    m_world->bindTexture(texture, item);
}

void MainWindow::setCameraTarget(const Item *item)
{
    m_itemsWidget->setCurrent(item);
}

void MainWindow::initializeGL()
{
    m_world->initializeGL();
}

void MainWindow::paintGL()
{
    m_world->paint();
}

void MainWindow::onItemSelected(const Item *item)
{
    if (item) {
        Item* itemc = const_cast<Item*>(item);
        Material* material = itemc->getMaterial();
        m_materialWidget->setMaterial(material);
        m_textureWidget->setCurrentTexture(item->getTexture());

        // set the target of the camera to look at that item
        m_world->getCamera()->setTargetPosition(item->getCurrentPosition());
        updateGLWidget();
    }
}

void MainWindow::onMaterialChanged()
{
    Item* item = const_cast<Item*>(m_itemsWidget->getCurrentItem());
    if (item) {
        item->setDirty();
    }
    updateGLWidget();
}

void MainWindow::onTextureChanged(const Texture *texture)
{
    Item* item = const_cast<Item*>(m_itemsWidget->getCurrentItem());
    m_world->bindTexture(texture, item);
    updateGLWidget();
}

void MainWindow::onZoomBy(int zoomFactor)
{
    m_world->getCamera()->zoom(zoomFactor / 1000.0);
    updateGLWidget();
}

void MainWindow::onMoveCameraBy(int x, int y)
{
    m_world->getCamera()->rotateCameraAroundTarget(x / 150.0 , y / 150.0);
    updateGLWidget();
}

void MainWindow::onRotateCameraBy(double angle)
{
    m_world->getCamera()->rotateCamera(angle / 100.0);
    updateGLWidget();
}

void MainWindow::onTimer(int t)
{
    Q_UNUSED(t)
    static double frame = 0.0;
    frame += m_speedSpinBox->value();
    m_world->getWorldItem()->setFrame(frame);
    const Item* item = m_itemsWidget->getCurrentItem();
    if (item) {
        m_world->getCamera()->setTargetPosition(item->getCurrentPosition());
    }
    updateGLWidget();
}

void MainWindow::onPlayPauseClicked()
{
    if (m_timeLine->state() == QTimeLine::Running) {
        m_timeLine->stop();
        m_playPauseButton->setText("Play");
    } else {
        m_timeLine->start();
        m_playPauseButton->setText("Pause");
    }
}

void MainWindow::updateGLWidget()
{
    m_glWidget->update();
}

void MainWindow::closeEvent(QCloseEvent *)
{
    qApp->quit();
}

void MainWindow::createMainContent()
{
    static bool firstTime = true;
    if (!firstTime) {
        qDebug() << "createLayout() can only be called once.";
        return;
    }
    firstTime = false;

    QWidget *mainWidget = new QWidget();
    QHBoxLayout *mainLayout = new QHBoxLayout();
    mainWidget->setLayout(mainLayout);
    setCentralWidget(mainWidget);

    QWidget *toolsWidget = new QWidget();
    QVBoxLayout *toolsLayout = new QVBoxLayout(toolsWidget);

    QSplitter *splitter = new QSplitter(Qt::Horizontal);
    mainLayout->addWidget(splitter);
    splitter->addWidget(m_glWidget);
    splitter->addWidget(toolsWidget);
    toolsLayout->addWidget(m_itemsWidget, 1);
    toolsLayout->addWidget(m_materialWidget);
    toolsLayout->addWidget(m_lightsWidget);
    toolsLayout->addWidget(m_textureWidget);
    toolsLayout->addWidget(m_cameraWidget);
    toolsLayout->addWidget(m_renderWidget);

    QHBoxLayout *hl = new QHBoxLayout();
    hl->addWidget(new QLabel("Speed Modifier"), 1, Qt::AlignRight);
    hl->addWidget(m_speedSpinBox);
    toolsLayout->addLayout(hl);

    hl = new QHBoxLayout();
    hl->addWidget(new QLabel("Gamma Correction"), 1, Qt::AlignRight);
    hl->addWidget(m_gammaCorrection);
    m_gammaCorrection->setRange(0, 10);
    m_gammaCorrection->setSingleStep(0.2);
    m_gammaCorrection->setValue(1.0);
    toolsLayout->addLayout(hl);

    toolsLayout->addWidget(m_playPauseButton);

    toolsLayout->setSpacing(0);
    m_speedSpinBox->setRange(0.0, 10.0);
    m_speedSpinBox->setSingleStep(0.2);
    m_speedSpinBox->setValue(0.6);
}

void MainWindow::makeSignalConnections()
{
    static bool firstTime = true;
    if (!firstTime) {
        qDebug() << "makeSignalConnections() can only be called once.";
        return;
    }
    firstTime = false;

    connect(m_glWidget, SIGNAL(requestInitializeGL()), this, SLOT(initializeGL()));
    connect(m_glWidget, SIGNAL(requestPaint()), this, SLOT(paintGL()));
    connect(m_glWidget, SIGNAL(GLresized(int,int)), m_world->getCamera(), SLOT(onWindowResized(int,int)));
    connect(m_glWidget, SIGNAL(moveCameraBy(int,int)), this, SLOT(onMoveCameraBy(int,int)));
    connect(m_glWidget, SIGNAL(zoomBy(int)), this, SLOT(onZoomBy(int)));
    connect(m_glWidget, SIGNAL(rotateCameraBy(double)), this, SLOT(onRotateCameraBy(double)));
    connect(m_glWidget, SIGNAL(sceneRenderTime(double)), m_renderWidget, SLOT(onSceneRenderTime(double)));
    connect(m_glWidget, SIGNAL(playPause()), this, SLOT(onPlayPauseClicked()));

    connect (m_renderWidget, SIGNAL(setPipelineParameter(QString,QVariant)), m_glWidget, SLOT(setPipelineParameter(QString,QVariant)));

    connect(m_itemsWidget, SIGNAL(currentItemChanged(const Item*)), this, SLOT(onItemSelected(const Item*)));
    connect(m_itemsWidget, SIGNAL(itemVisibleChanged()), this, SLOT(updateGLWidget()));

    connect(m_materialWidget, SIGNAL(changed()), this, SLOT(onMaterialChanged()));

    connect(m_textureWidget, SIGNAL(textureChanged(const Texture*)), this, SLOT(onTextureChanged(const Texture*)));

    connect(m_timeLine, SIGNAL(frameChanged(int)), this, SLOT(onTimer(int)));
    connect(m_playPauseButton, SIGNAL(clicked()), this, SLOT(onPlayPauseClicked()));

    connect(m_lightsWidget, SIGNAL(lightModified()), this, SLOT(updateGLWidget()));
    connect(m_cameraWidget, SIGNAL(cameraChanged()), this, SLOT(updateGLWidget()));
}

